package org.ligson.org.util;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.*;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class MyWeightedResponseTimeRule extends WeightedResponseTimeRule {
    static AtomicInteger count1 = new AtomicInteger(0);
    static AtomicInteger count2 = new AtomicInteger(0);
    static AtomicInteger count3 = new AtomicInteger(0);

    public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;

    private int serverWeightTaskTimerInterval = DEFAULT_TIMER_INTERVAL;

    // holds the accumulated weight from index 0 to current index
    // for example, element at index 2 holds the sum of weight of servers from 0 to 2
    private volatile List<Double> accumulatedWeights = new ArrayList<Double>();


    private final Random random = new Random();

    String name = "unknown";

    void initialize(ILoadBalancer lb) {
        if (serverWeightTimer != null) {
            serverWeightTimer.cancel();
        }
        serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-"
                + name, true);
        serverWeightTimer.schedule(new MyWeightedResponseTimeRule.DynamicServerWeightTask(), 0,
                serverWeightTaskTimerInterval);
        // do a initial run
        MyWeightedResponseTimeRule.ServerWeight sw = new MyWeightedResponseTimeRule.ServerWeight();
        sw.maintainWeights();

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                log
                        .info("Stopping NFLoadBalancer-serverWeightTimer-"
                                + name);
                serverWeightTimer.cancel();
            }
        }));
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        if (lb instanceof BaseLoadBalancer) {
            name = ((BaseLoadBalancer) lb).getName();
        }
        initialize(lb);
    }

    @Override
    public Server choose(Object key) {
        Server server = mychoose(getLoadBalancer(), key);
        log.info("负载均衡结果:{}", server != null ? server.getHostPort() : null);
        AbstractLoadBalancer lb = (AbstractLoadBalancer) getLoadBalancer();
        LoadBalancerStats stats = lb.getLoadBalancerStats();
        if (server != null) {
            ServerStats sss = stats.getSingleServerStat(server);
            log.info("server stat:{}", sss);
            if ("10.6.239.123:8891".equalsIgnoreCase(server.getHostPort())) {
                count1.incrementAndGet();
            } else if ("10.6.239.123:8892".equalsIgnoreCase(server.getHostPort())) {
                count2.incrementAndGet();
            } else if ("10.6.239.123:8893".equalsIgnoreCase(server.getHostPort())) {
                count3.incrementAndGet();
            }
            int sum = (count1.get() + count2.get() + count3.get());
            double count1rate = count1.get() * 1.00 / sum;
            double count2rate = count2.get() * 1.00 / sum;
            double count3rate = count3.get() * 1.00 / sum;
            log.info("\n sum:{} \nserver1 rate:{}%    {}\nserver2 rate:{}%    {}\nserver3 rate:{}%      {}\n", sum, count1rate * 100, count1.get(), count2rate * 100, count2.get(), count3rate * 100, count3.get());

        }

        return server;
    }

    //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    public Server mychoose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        for (int i = 0; i <1000; i++) {
            System.out.println(lb.getAllServers());
        }




        while (server == null) {
            // get hold of the current reference in case it is changed from the other thread
            List<Double> currentWeights = accumulatedWeights;
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> allList = lb.getAllServers();


            int serverCount = allList.size();

            if (serverCount == 0) {
                return null;
            }

            int serverIndex = 0;

            // last one in the list is the sum of all weights
            double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);
            // No server has been hit yet and total weight is not initialized
            // fallback to use round robin
            if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {
                server = super.choose(getLoadBalancer(), key);
                if (server == null) {
                    return server;
                }
            } else {
                // generate a random weight between 0 (inclusive) to maxTotalWeight (exclusive)
                double ran = random.nextDouble();
                double randomWeight = ran * maxTotalWeight;
                log.info("\n{}\n{}\n-----randomWeight:{}  maxTotalWeight:{},nextDouble:{}",allList, accumulatedWeights, randomWeight, maxTotalWeight, ran);

                // pick the server index based on the randomIndex
                int n = 0;
                for (Double d : currentWeights) {
                    if (d >= randomWeight) {
                        serverIndex = n;
                        break;
                    } else {
                        n++;
                    }
                }

                server = allList.get(serverIndex);
            }

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Next.
            server = null;
        }
        return server;
    }

    class DynamicServerWeightTask extends TimerTask {
        public void run() {
            MyWeightedResponseTimeRule.ServerWeight serverWeight = new MyWeightedResponseTimeRule.ServerWeight();
            try {
                serverWeight.maintainWeights();
            } catch (Exception e) {
                log.error("Error running DynamicServerWeightTask for {}", name, e);
            }
        }
    }

    class ServerWeight {

        public void maintainWeights() {
            ILoadBalancer lb = getLoadBalancer();
            if (lb == null) {
                return;
            }

            if (!serverWeightAssignmentInProgress.compareAndSet(false, true)) {
                return;
            }

            try {
                log.info("Weight adjusting job started");
                AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
                LoadBalancerStats stats = nlb.getLoadBalancerStats();
                if (stats == null) {
                    // no statistics, nothing to do
                    return;
                }
                double totalResponseTime = 0;
                // find maximal 95% response time
                for (Server server : nlb.getAllServers()) {
                    // this will automatically load the stats if not in cache
                    ServerStats ss = stats.getSingleServerStat(server);
                    totalResponseTime += ss.getResponseTimeAvg();
                }
                // weight for each server is (sum of responseTime of all servers - responseTime)
                // so that the longer the response time, the less the weight and the less likely to be chosen
                Double weightSoFar = 0.0;

                // create new list and hot swap the reference
                List<Double> finalWeights = new ArrayList<Double>();
                for (Server server : nlb.getAllServers()) {
                    ServerStats ss = stats.getSingleServerStat(server);
                    double weight = totalResponseTime - ss.getResponseTimeAvg();
                    weightSoFar += weight;
                    finalWeights.add(weightSoFar);
                }
                setWeights(finalWeights);
            } catch (Exception e) {
                log.error("Error calculating server weights", e);
            } finally {
                serverWeightAssignmentInProgress.set(false);
            }

        }
    }

    void setWeights(List<Double> weights) {
        this.accumulatedWeights = weights;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        super.initWithNiwsConfig(clientConfig);
        serverWeightTaskTimerInterval = clientConfig.get(WEIGHT_TASK_TIMER_INTERVAL_CONFIG_KEY, DEFAULT_TIMER_INTERVAL);
    }
}
